home *** CD-ROM | disk | FTP | other *** search
/ Clickx 65 / Clickx 65.iso / software / internet / xmarks / xmarks-3.1.1.xpi / chrome / content / foxmarks-network.js < prev    next >
Encoding:
Text File  |  2009-05-05  |  13.7 KB  |  352 lines

  1. /* 
  2.  Copyright 2007 Foxmarks Inc.
  3.  
  4.  foxmarks-network.js: implements network interface to Syncd2.
  5.  
  6.  */
  7.  
  8. // TO DO:
  9. // * Tie in to global notification system.
  10. // * Implement dumb server support.
  11. // * Setup Wizard support for upgrade process, including self-served.
  12. // * UI changes to support deprecation of self-serving.
  13. // * Once sync works, dismantle RDF sync crap, old network crap, etc.
  14. // * Add upgrade/version compatibility to syncd2 spec.
  15. // * Contemplate server status/feedback control for syncd2.
  16.  
  17. const NS_ERROR_REDIRECT_LOOP = 2152398879;
  18. const NS_ERROR_CWD_ERROR = 0x804b0016;
  19.  
  20. /*
  21.  
  22.  The Request class handles communication with a server. It is essentially
  23.  an HTTP Javascript remote object broker, delivering objects from the client
  24.  to the server and retrieving Javascript objects from the server in return.
  25.  Request has built-in support for the Foxmarks authentication protocol,
  26.  initiated by a 302 Redirect response.
  27.  
  28.  Arguments:
  29.      method: a string indicating which HTTP method to use.
  30.      url: either a string or an object containing any of
  31.         protocol, host, and path. If the caller provides a string, that
  32.         literal string is used as the target url. If an object is
  33.         supplied, Request uses whatever components are provided to construct
  34.         a final url, employing defaults for missing components.
  35.      obj: the object to be transmitted to the server.
  36.      isAuthRequest: a boolean which, if set, indicates that this request is
  37.         an authentication request. This is used internally only and should
  38.         never be set by callers.
  39.     headers: a dict of HTTP headers that can be optionally set on the request.
  40.  
  41.  Return value:
  42.     Three different types of errors can occur in processing a request.
  43.     1) Network error: DNS failure, connection reset, etc.
  44.     2) Transport failure: HTTP 404, etc.
  45.     3) App failure: syncd rejects a request because of revision mismatch.
  46.  
  47.  If an error occurs, we return an integer status code in response.status.
  48.  
  49.  NOTE: We're deprecating the use of "restype" -- clients should rely on
  50.  the status return value, which is 0 is everything went as expected
  51.  or a non-zero integer representing the error code if things went awry.
  52.  
  53.  As of this moment, Acctmgr still uses restype, but clients calling this
  54.  code should rely entirely on status, as restype is gone.
  55.  
  56.  */
  57.  
  58.  
  59. var g_auth = "";
  60. var g_authu = "";
  61. var g_authp = "";
  62.  
  63. function Request(method, url, bodyobj, isAuthRequest, headers, ignoreBody, ignoreAuthTokens) {
  64.     this._channel = null;
  65.     this._streamLoader = null;
  66.     this._callback = null;
  67.     this._triedAuth = false;
  68.     this._headers = headers;
  69.     this._ignoreBody = ignoreBody;
  70.     this._ignoreAuthTokens = ignoreAuthTokens;
  71.     try {
  72.         this.JSON = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
  73.     } catch(e){
  74.         this.JSON = null;
  75.     }
  76.     
  77.     // Argument url is either a string or an object
  78.     // consisting of protocol, host, and path.
  79.     // If it's a string, just take the string that's been
  80.     // given. If it's an object, assemble the components
  81.     // (applying defaults as appropriate) into a url.
  82.  
  83.     if (url instanceof Object) {
  84.  
  85.         // Set up defaults.
  86.         if (!url.protocol) {
  87.             if (gSettings.securityLevel == 1 ||
  88.                 (gSettings.securityLevel == 0 && isAuthRequest)) {
  89.                     url.protocol = "https";
  90.             } else {
  91.                 url.protocol = "http";
  92.             }
  93.         }
  94.         if (!url.host) url.host = gSettings.host;
  95.         if (!url.path) url.path = "/";
  96.         if (url.path[0] != "/") url.path = "/" + url.path;
  97.         this._url = url.protocol + "://" + url.host + url.path;
  98.     } else {
  99.         this._url = url;
  100.     }
  101.     this._bodyobj = bodyobj;
  102.     this._method = method;
  103.     this._isAuthRequest = isAuthRequest;
  104. }
  105.  
  106. Request.prototype = {
  107.  
  108.     /*
  109.  
  110.     Process a request. Given the protocol, host, and path, construct a
  111.     url and execute the specified method against that url, inserting
  112.     the given message body.
  113.  
  114.     When request completes, it calls the provided callback function,
  115.     passing a single object (the "response object"). 
  116.  
  117.     */
  118.  
  119.     Start: function(callback) {
  120.         LogWrite(">>> " + this._method + " " + this._StripPassword(this._url));
  121.         if (this._bodyobj && !this._isAuthRequest) {
  122.             LogWrite(">>> Body is: " + 
  123.                 (!gSettings.getDebugOption("no-verbose") ?
  124.                 this._bodyobj.toJSONString() : "(disabled)"));
  125.         }
  126.         this._callback = callback;
  127.  
  128.         // Create a channel.
  129.         var ios = Cc["@mozilla.org/network/io-service;1"].
  130.             getService(Ci.nsIIOService);
  131.         try {
  132.             this._channel = ios.newChannelFromURI(ios.newURI(this._url, 
  133.                     "UTF-8", null));
  134.         } catch (e) {
  135.             LogWrite("Couldn't create channel from " + this._url + ", error:" + e.toSource());
  136.             callback(1008);
  137.             return;
  138.         }
  139.  
  140.         this._channel.QueryInterface(Ci.nsIUploadChannel);
  141.         this._channel.loadFlags |= 
  142.             Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE;
  143.  
  144.         // If we have a body to transmit, set it up as an upload stream.
  145.         if (this._bodyobj) {
  146.             var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
  147.                 createInstance(Ci.nsIScriptableUnicodeConverter);
  148.             converter.charset = "UTF-8";
  149.             var stream = converter.convertToInputStream(
  150.                 this._bodyobj.toJSONString());
  151.             this._channel.setUploadStream(stream, "application/json", -1);
  152.         }
  153.  
  154.         // Special setup only for HTTP channels.
  155.         if (this._channel instanceof Ci.nsIHttpChannel) {
  156.  
  157.             // Set the channel's method if necessary.
  158.             if (this._method) {
  159.                 this._channel.requestMethod = this._method;
  160.             }
  161.  
  162.             // Disable redirection.
  163.             this._channel.redirectionLimit = 0;
  164.             this._channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
  165.  
  166.             // Set the user agent.
  167.             this._channel.setRequestHeader("User-Agent", 
  168.                 this._channel.getRequestHeader("User-Agent") + 
  169.                     " Xmarks-Fx/" + FoxmarksVersion(), false);
  170.  
  171.             if(!this._ignoreAuthTokens){
  172.                 if(g_authp != gSettings.password || g_authu != gSettings.username)
  173.                     g_auth = "";
  174.                 if(g_auth.length > 0){
  175.                     this._channel.setRequestHeader("Authorization", 
  176.                         "XMAuth " + g_auth, false);
  177.                     this._channel.setRequestHeader("X-Xmarks-Auth",
  178.                         g_auth, false);
  179.                 }
  180.             }
  181.  
  182.             // Set other headers if provided.
  183.             if (this._headers) {
  184.                 var self = this;
  185.                 forEach(this._headers, function(v, k) {
  186.                     self._channel.setRequestHeader(k, v, false);
  187.                 } );
  188.             }
  189.         }
  190.  
  191.         // Create a stream loader for retrieving the response.
  192.         this._streamLoader = Cc["@mozilla.org/network/stream-loader;1"]
  193.             .createInstance(Ci.nsIStreamLoader);
  194.  
  195.         // Fire it up. Note that this results in the channel's AsyncOpen
  196.         // being called; if we have set an upload stream above, it will be
  197.         // transmitted as part of the request. This is a bit strange, but
  198.         // appears to be correct.
  199.         try {
  200.             // Before Firefox 3...
  201.             this._streamLoader.init(this._channel, this, null);
  202.         } catch(e) {
  203.             // Firefox 3 style...
  204.             this._streamLoader.init(this);
  205.             this._channel.asyncOpen(this._streamLoader, null);
  206.         }
  207.         return;
  208.     },
  209.  
  210.     Cancel: function() {
  211.         if (this._channel && this._channel.isPending()){
  212.             LogWrite("Cancelling Network Request");
  213.             this._channel.cancel(0x804b0002);
  214.         }
  215.     },
  216.  
  217.     onStreamComplete: function(loader, ctxt, status, resultLength, result) {
  218.         var response = {};
  219.         if (Components.isSuccessCode(status)) {
  220.             try {
  221.                 status = this._channel.responseStatus || 200;
  222.             } catch (e) {
  223.                 this._callback( { status: 1005, errormsg: 
  224.                         "Disable automatic proxy settings detection."} );
  225.                 return;
  226.             }
  227.  
  228.             //try {
  229.             //    dump("cookie: " + this._channel.getResponseHeader("Set-Cookie") + "\n");
  230.            // }catch(e){}
  231.             var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
  232.                 createInstance(Ci.nsIScriptableUnicodeConverter);
  233.             converter.charset = "utf-8";
  234.             var msg = "";
  235.             if (status == 200 || status == 201 || status == 204) {
  236.                 if(!this._ignoreBody){
  237.                     msg = converter.convertFromByteArray(result, resultLength);
  238.                 }
  239.                 if (msg && msg.length && !this._ignoreBody) {
  240.                     try {
  241.                         response = this.JSON ? this.JSON.decode(msg) :
  242.                             eval("(" + msg + ")");
  243.                         if(response.auth && response.auth.length > 0){
  244.                             g_auth =  response.auth;
  245.                         }
  246.                     } catch(e) {
  247.                         LogWrite("Failed to parse message: " + msg);
  248.                         response = { status: 1010, 
  249.                             errormsg: "Invalid server response" }; 
  250.                     }
  251.                 }
  252.                 if (response.status == null) {
  253.                     response.status = 0;
  254.                 }
  255.  
  256.                 // New Auth stuff
  257.                 if(response.status == 302){
  258.                     function RestartAfterAuth(response) {
  259.                         if (response.status == 0){
  260.                             g_auth = response.auth;
  261.                             g_authp = gSettings.password;
  262.                             g_authu = gSettings.username;
  263.                             // We're authenticated. Retry original request.
  264.                             self.Start(self._callback);
  265.                             return;
  266.                         } else {
  267.                             // Auth failed. Return failure code.
  268.                             if (response.message == "Wrong username or password.") {
  269.                                 response.status = 401;
  270.                             }
  271.                             self._callback(response);
  272.                             return;
  273.                         }
  274.                     }
  275.  
  276.                     LogWrite(">>> Authenticating...");
  277.  
  278.                     if (this._triedAuth) {
  279.                         LogWrite("In authentication loop. Possible proxy server caching issue.");
  280.                         this._callback( { status: 1012 } );
  281.                         return;
  282.                     }
  283.  
  284.                     var location = response.authtoken_location;
  285.                     var self = this;    // keep a handle on the original request
  286.                     // parse out the protocol, host, and path
  287.                     var colonslashslash = location.indexOf("://");
  288.                     var nextslash = location.indexOf("/", colonslashslash + 3);
  289.                     if (colonslashslash < 0 || nextslash < 0) {
  290.                         throw Error("Couldn't parse location string " + location);
  291.                     }
  292.                     var url = {}
  293.                     url.host = location.substr(colonslashslash + 3, 
  294.                         nextslash - (colonslashslash + 3)); 
  295.                     url.host = gSettings.getCharPref('host-login', url.host);
  296.  
  297.                     url.path = location.substr(nextslash);
  298.                     try {
  299.                         var pw = gSettings.password;
  300.                     } catch (e) {
  301.                         LogWrite("User canceled password request.");
  302.                         this._callback(2);
  303.                         return;
  304.                     }
  305.                     var authReq = new Request("POST", url,
  306.                         { username: gSettings.username, 
  307.                             password: Base64.encode(pw) }, true );
  308.                     this._triedAuth = true;
  309.                     authReq.Start(RestartAfterAuth);
  310.                     return;
  311.                 }
  312.                 
  313.             } else {
  314.                 response = { status: status, "errormsg" : msg };
  315.             }
  316.             try {   // Pass back the etag if there is one.
  317.                 response.etag = this._channel.getResponseHeader("Etag");
  318.             } catch (e) {}
  319.  
  320.             if(gSettings.getDebugOption("no-verbose")){
  321.                 LogWrite(">>> Callback (disabled)");
  322.             } else {
  323.                 var oldauth = response.auth;
  324.                 delete response.auth;
  325.                 LogWrite(">>> Callback " + response.toSource());
  326.                 response.auth = oldauth;
  327.             }
  328.             this._callback(response);
  329.             return;
  330.         } else {
  331.             if (status == NS_ERROR_REDIRECT_LOOP && !this._isAuthRequest) {
  332.                 this._callback( { status: status });
  333.                 return;
  334.             } else if (status == NS_ERROR_CWD_ERROR) {
  335.                 LogWrite("CWD error (mapping to 404)");
  336.                 this._callback( { status: 404 } );
  337.             } else {
  338.                 LogWrite("network request failed; status is " +
  339.                         status.toString(16));
  340.                 this._callback( { status: status });
  341.             }
  342.         }
  343.     },
  344.  
  345.     _StripPassword: function(url) {
  346.         var exp = /^(.*):(.*)@(.*)$/
  347.         var match = url.match(exp);
  348.         return match ? match[1] + "@" + match[3] : url;
  349.     }
  350. }
  351.  
  352.